URL State
Patterns for persisting application state in the URL — query params, compression, and deep links.
Storing UI state in the URL makes it shareable, bookmarkable, and back-button-friendly. The URL Dialog page covers the simplest case — a single boolean toggle. This page covers the general problem: arbitrary state in query parameters, and what to do when it gets too large.
Query params with nuqs
nuqs is a type-safe search-params state manager for
React (~6 kB gzipped). It gives you a useState-like API where the value
lives in the URL instead of component state:
import { useQueryState, parseAsInteger } from "nuqs";
const [search, setSearch] = useQueryState("q", { defaultValue: "" });
const [page, setPage] = useQueryState("page", parseAsInteger.withDefault(1));Key features:
- Built-in parsers for strings, integers, floats, booleans, enums, dates, JSON, and more. Custom serializers are straightforward.
- History control — choose
push(adds a back-stack entry) orreplace(overwrites the current one) per setter call. - Batched updates via
useQueryStates— update multiple params in a single URL write, avoiding intermediate states. - Framework support — Next.js (App Router + Pages Router), Remix, React Router, TanStack Router, and plain React SPAs.
- Server-component safe — shallow updates (the default) don't trigger a server round-trip; opt in to server re-rendering when needed.
When to reach for nuqs
| Scenario | Tool |
|---|---|
| One boolean (modal open/close) | useUrlToggle — zero deps, minimal |
| A handful of typed params (search, filters, page) | nuqs — built-in parsers, batching, framework integration |
| Complex nested object or very large state | Compression (see below) or server-side storage with a URL key |
Compressing state into the URL
When the state outgrows a few simple params — think a full editor layout, a graph visualization, or a playground config — you can compress it into a single query parameter:
JSON.stringifythe state object.- Compress with a deflate library (pako,
the browser's built-in
CompressionStream, or a text-specific compressor). - Base64-encode the result for URL safety.
import pako from "pako";
function encode(state: unknown): string {
const json = JSON.stringify(state);
const compressed = pako.deflate(json);
return btoa(String.fromCharCode(...compressed));
}
function decode<T>(encoded: string): T {
const bytes = Uint8Array.from(atob(encoded), (c) => c.charCodeAt(0));
const json = pako.inflate(bytes, { to: "string" });
return JSON.parse(json);
}Usage:
// Save
const url = new URL(window.location.href);
url.searchParams.set("s", encode(appState));
history.replaceState(null, "", url);
// Restore
const encoded = new URL(window.location.href).searchParams.get("s");
if (encoded) loadState(decode(encoded));URL length limits
Compression helps, but URLs are not unlimited:
| Environment | Practical limit |
|---|---|
| Older proxies / CDNs / email clients | ~2 000 chars |
| Internet Explorer (legacy) | ~2 083 chars |
| Modern Chrome | ~32 000 chars |
| Safari | ~64 000 chars |
| Firefox | ~300 000+ chars |
The safe ceiling is ~2 000 characters if you need to support copy-paste through email, Slack previews, or QR codes. Deflate typically achieves 40–50% compression on JSON, so you can fit roughly 1.5 KB of raw state in a URL-safe payload.
Text-specific compression
General-purpose deflate compresses bytes. If your state is predominantly text (think: a code playground, a markdown editor, a DSL config), a compressor designed for short strings can do better:
- Unishox2 — a hybrid entropy + dictionary compressor optimised for short Unicode strings. Achieves higher compression ratios than deflate on strings under ~1 KB, especially those with natural-language patterns or repeated Unicode code points.
- lz-string — LZ-based
compression with a built-in
compressToEncodedURIComponentoutput mode that avoids the base64 round-trip entirely. - smol-string — Huffman + back-reference compressor for short strings, outputs URL-safe base64 directly.
Further reading
- nuqs documentation — the recommended starting point for query-param state in React.
- Storing large web app state in URL using pako — the technique and caveats for deflate-in-URL.