링크에 담는 화면 상태

URL State And History

Keep shareable screen state consistent across links, refresh, and browser history.

GitHub에서 원문 보기 ↗새 탭
원문 가이드 ENGLISH

Repository Boundary

Keep shareable screen state consistent across links, refresh, and browser history. This pattern belongs to Design Engineering and adds no Layout CSS or shared store implementation.

Reusable Method

Use for committed search, filters, pagination, and selected IDs that should be shareable. Keep secrets, sensitive drafts, and hover state out of URLs.

State Model And Ownership

The route owns committed query state; a text field may own an uncommitted buffer. Parsing supplies validated defaults. User commits write URL parameters; history navigation reads them without writing a new entry.

Transitions

Event and precondition Next state Effect or invariant
Open or history traversal Parsed route state Render without another push
Commit filter New route; page reset Push a meaningful navigation entry
Normalize invalid input Validated defaults Replace only under declared canonicalization policy

Minimal Executable Example

Run this standalone JavaScript block with Node.js 22 or newer, or run all pattern examples from a repository checkout with npm run test:state-management. The assertions exercise the local model, not a browser or backend.

import assert from "node:assert/strict";
function readRoute(href) {
  const u = new URL(href);
  const raw = u.searchParams.get("page") ?? "1";
  const n = /^\d+$/.test(raw) ? Number(raw) : NaN;
  return { q: u.searchParams.get("q") ?? "", page: Number.isSafeInteger(n) && n > 0 ? n : 1 };
}
function commitQuery(href, q) {
  const u = new URL(href);
  if (q) u.searchParams.set("q", q); else u.searchParams.delete("q");
  u.searchParams.delete("page");
  return u.href;
}
const original = "https://example.test/items?q=A&page=3&view=grid#results";
const changed = commitQuery(original, "B & C");
assert.deepEqual(readRoute(changed), { q: "B & C", page: 1 });
assert.equal(new URL(changed).searchParams.get("view"), "grid");
assert.equal(new URL(changed).hash, "#results");
assert.deepEqual(readRoute(original), { q: "A", page: 3 }); // Back reads old URL.
assert.equal(readRoute("https://example.test/?page=Infinity").page, 1);

What Breaks If Removed

Writing a new history entry in response to a history event creates a loop. Keeping the old page after changing the filter can display an apparently empty collection. Rebuilding the URL from scratch can drop unrelated parameters or fragments.

Composition And Substitution

Use latest request wins for data reads and unsaved navigation before committing a destructive route change. Reset local input from history only under the declared editing policy.

Opinionated Guidance

Decide push versus replace from meaningful navigation steps. Do not make every keystroke a Back-button stop by accident.

Platform-Specific Guidance

Browser adapters must handle popstate and their router's navigation API; the pure URL functions do not install listeners. SSR and client must use the same parser and defaults.

Platform reference: MDN API documentation, checked 2026-09-16. This supports the named browser boundary; the model and product policy remain local synthesis.

Unsupported Absolutes

A URL is not a safe persistence medium for private data, nor does parsing prove authorization to access a selected entity.

Verification Contract

  • Commit query B from page 3; reset to page 1 and preserve unrelated parameters.
  • Back, Forward, reload, and paste a deep link; displayed controls agree with the URL.
  • Reject malformed page values; avoid URL-to-state-to-URL loops.

Record actual outcomes separately using the verification matrix.

Source, License, And Attribution

Locally authored synthesis and examples. No upstream implementation is copied. These experimental contracts describe consumer-owned behavior; review when a failure, ownership change, or platform change invalidates an assumption.

IA Navigation

Parent: State Management. Next: Continue the state management route.