Keep UI state in the URL
Put filters, tabs, pages and open panels in query params.
From web-design-guidelines (Web Interface Guidelines). Library: nuqs.
Reload the page
Filter, page and search, then press Reload.
State in memoryBad
useState("all")State in the URLGood
useQueryState("status")const [status, setStatus] = useQueryState("status",
parseAsStringLiteral(STATUSES).withDefault("all").withOptions({ history: "push" }));
const [page, setPage] = useQueryState("page", parseAsInteger.withDefault(1));
const [q, setQ] = useQueryState("q", { defaultValue: "" });
Share a link
Open Payments, expand a row, then press Share both.
State in memoryBad
useState("overview")State in the URLGood
?tab=payments&open=42function setView(patch) {
const url = new URL(location.href);
for (const [k, v] of Object.entries(patch)) v == null ? url.searchParams.delete(k) : url.searchParams.set(k, v);
history.pushState(null, "", url);
render(readView());
}
addEventListener("popstate", () => render(readView()));
Press Back
Switch tabs a few times, then press Back.
State in memoryBad
useState("general")State in the URLGood
history: "push" | "replace"const [tab, setTab] = useQueryState("tab",
parseAsStringLiteral(TABS).withDefault("general").withOptions({ history: "push" }));
const [q, setQ] = useQueryState("q",
parseAsString.withDefault("").withOptions({ history: "replace" }));
Why it matters, pros and cons
Why it matters
The URL is the only state the browser saves and shares; memory dies on reload.
Pros
- Reload restores the exact view.
- A link shows exactly what you saw.
- Back and Forward step through views.
Cons
- Skip transient UI: hover, open menus, drafts.
- Never put secrets in URLs; they get logged.
- Params are user input; validate them.