惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
Microsoft Security Blog
Microsoft Security Blog
B
Blog
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
爱范儿
爱范儿
博客园_首页
博客园 - 聂微东
量子位
V
Visual Studio Blog
aimingoo的专栏
aimingoo的专栏
T
The Blog of Author Tim Ferriss
J
Java Code Geeks
小众软件
小众软件
大猫的无限游戏
大猫的无限游戏
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
N
Netflix TechBlog - Medium
F
Fortinet All Blogs
The Cloudflare Blog
T
Tailwind CSS Blog
G
Google Developers Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
月光博客
月光博客
腾讯CDC

Echo JS

billboard.js 4.1.0: Live resizing, configurable subchart, React subpath & CSP-safe worker From 1,256ms to 96ms: Fixing INP in a Massive React Dropdown GitHub - evoluteur/cymatics: Play a frequency and watch the sand settle into its Chladni figure, computed from the wave equation. Memdeklaro - The Basics of Decentralized Identity (DID) and Self-Sovereign Identity (SSI) How Railmid Works GitHub - evoluteur/platonic-solids: Turn the five Platonic solids in 3D, show their duals, read their measurements, and print the nets to fold your own. GitHub - evoluteur/sacred-geometry: Sacred Geometry Generator: draw, tune, and export Vesica Piscis, Seed of Life, Flower of Life, Metatron's Cube, and the Golden Spiral as SVG Best of Self-Sovereign Identity: Digitalcourage, World Passport and Memdeklaro Reads Are Subscriptions - Migrating from Zustand to Coaction GitHub - evoluteur/binaural-beats: Simple web page to play binaural beats for sleep, meditation, relaxation, and focus: Delta, Theta, Alpha, Beta, and Gamma brainwave frequencies, with an optional pink or brown noise bed. toast-queue — Accessible, customizable toast notifications Building a High-Performance Data Grid in React, Vue, and Svelte I built a flight recorder for AI sessions React Authentication With JWT, Zustand, and Axios | JavaScript Tools Blog My idempotency library had one job. A dropped connection made it run the payment twice. "half-open" twice is not the same state: the bug that shaped breakwater 1.0 GitHub - evoluteur/evolutility-server-node: Framework for building REST APIs for CRUD with models rather than code (using Node.js, Express, and PostgreSQL). React Router v8 in Action: Lazy Loading and Nested Routes One $ for every environment | Xec My test suite had 100% coverage. Mutation testing still found real bugs. The type-safe data layer for Kysely | Kysera What JavaScript Obfuscation in the AI Era | JavaScript Tools Blog Using Mongoose Studio with Apache Cassandra via Data API GitHub - trekhleb/yesbrainer: 🧠 A council of AI models for the decisions that aren't no-brainers — they answer in parallel, debate to consensus, or get judged to a verdict. Browser-only, open source, bring your own keys (BYOK), no backend. Node.js has plenty of circuit breakers. So why did I build another one? My Redis library said the write succeeded. Redis was down. GitHub - Techthos/gadget: Prebuilt, interactive HTML widgets for MCP Apps in Go — data tables and forms, self-contained in a single binary, host-themed, spec-compliant. GitHub - evoluteur/react-morph-charts: React component for bubble chart, bar chart, and pie chart, with animated morphing transitions between charts, on hover, and on window resize. Interactive Metaballs Tutorial
Sharing Application State in a URL
2026-09-03 · via Echo JS

Most links simply point to a page. Sometimes users also need to share the page’s current state, such as the selected filters or the contents of an editor. Query parameters are often enough for this, but as the amount of data grows, you also need to consider how to encode and compress it.

I ran into this while building Schemagic, a visual JSON Schema editor. The site has no user accounts, so schemas are shared through URLs, just like code in TypeScript Playground and similar tools.

When to store state in a URL

Before looking at encoding and compression, consider whether URL-based storage fits the use case.

A compressed payload is no longer human-readable, but that is usually fine when the original data would be impractical to edit manually. For state with only a few parameters, however, query parameters are a better choice: a URL such as ?type=article is easy to understand and edit directly in the address bar.

URL-based storage has limitations. It does not support collaborative editing or access controls, and once you share the data, you cannot revoke it. Those features require storing the state on the backend.

Where to store state in a URL

You can store state in three parts of a URL: the path, query parameters, or the fragment.

The path usually identifies the page itself rather than its current state.

Query parameters work well for small bits of state, such as filters, sorting, or the active editor. The catch is that the browser sends them to the server whenever someone opens the link. Schemagic schemas can be large and may contain private data, so sending them to the backend is not an option.

The URL fragment is the best fit. The fragment is the part after #, and browsers do not include it in HTTP requests. This keeps the state in the browser instead of sending it to the server when someone opens the URL. TypeScript Playground takes the same approach and stores the code in a fragment: #code/MYewdgzgLgBArgJwDYwLwwEQAspQA4QBcA9MQJYBuAhgNZlgB0AJgKYXEYDcQA.

Serialization and encoding

A naive approach is to serialize the object as JSON and append the resulting string directly to the URL:

const dataStr = JSON.stringify(dataObj);

const url = `${BASE_URL}#${dataStr}`;

This approach has two problems: some JSON characters require percent-encoding, and the resulting string may be too long.

Compression addresses the second problem, but it produces binary data that cannot be added to a URL directly. The compressed data must first be converted to URL-safe text. The js-base64 library can encode it as Base64url, a variant of Base64 with a URL-safe alphabet.

The encoding pipeline is state → serialization → compression → Base64url. Decoding follows the same steps in reverse.

Browser history

In TypeScript Playground, the URL changes only after the editor loses focus, and navigating through the browser history does not restore previous code states. When state changes often, compressing it after every edit can slow down the interface. Debouncing URL updates avoids this by running compression only after a short pause.

Creating a history entry for every edit would clutter the browser history and make the Back button undo changes one by one instead of leaving the page. You can use history.replaceState() to update the URL without creating a new entry or reloading the page.

URL length limits

URL length limits depend on the browser and how the link is shared. For example, an email client or messaging app may truncate a long link. There is no universal safe maximum, so choose a project-specific limit based on the amount of state the application needs to support, then test URLs near that limit in the browsers and apps users are likely to use.

Compression methods

When choosing a compression method, consider its compression ratio, compression and decompression speed, effect on the client bundle size, and browser support. I compared lz-string, pako, fflate, and the native CompressionStream in two formats: deflate-raw and brotli.

For the benchmarks, I used two JSON schemas: GitHub Funding and JSON Resume. I used Vitest’s benchmarking tools in Browser Mode to measure the length of each compressed and encoded payload, along with compression and decompression times.

Encoded state size

Method GitHub Funding JSON Resume
without compression 2,248 (100%) 8,833 (100%)
pako 1,112 (49%) 3,043 (34%)
fflate 1,112 (49%) 3,095 (35%)
lz-string 1,729 (77%) 5,125 (58%)
CompressionStream deflate-raw 1,099 (49%) 3,004 (34%)
CompressionStream brotli 888 (40%) 2,295 (26%)
Schema sizes after encoding and compression, in characters

For both schemas, lz-string produced longer output than the alternatives. pako, fflate, and CompressionStream with deflate-raw produced output of similar length, which is expected since they all use the DEFLATE codec.

Compression and decompression speed

  • GitHub Funding
  • JSON Resume

012345678↑ Time (ms)pakofflatelz-stringCompressionStreamdeflate-rawCompressionStreambrotli0.1490.3360.1010.2680.3591.440.1730.3693.468.95

Compression time
  • GitHub Funding
  • JSON Resume

00.050.10.150.20.250.30.350.4↑ Time (ms)pakofflatelz-stringCompressionStreamdeflate-rawCompressionStreambrotli0.04670.1340.1040.1770.1440.4360.1310.2020.1360.207

Decompression time

In these benchmarks, lz-string produced larger output and compressed and decompressed more slowly than the DEFLATE-based alternatives, so I did not consider it further.

CompressionStream with brotli took noticeably longer to compress the data, and browser support for the format is still limited. Use it only when minimizing URL length is critical and all target browsers support it.

Bundle size

CompressionStream is built into the browser, so it doesn’t require an external library or add anything to the client bundle. The gzipped size of fflate is 4.61 KB, compared with 15 KB for pako. Since the two libraries have nearly identical performance and compression ratios, fflate’s smaller bundle makes it the better choice.

Choosing a compression method

That leaves two practical options with similar speed and compression ratios: CompressionStream with deflate-raw and fflate.

CompressionStream keeps the client bundle smaller but requires browser support for the API. fflate works without the native API and provides more control over compression, including support for custom dictionaries.

For Schemagic, I chose fflate and use the #dr:... prefix to identify the deflate-raw format. This lets me switch formats later without breaking existing links.

Putting it together

Before compressing state into a URL, make sure this approach fits the use case. For small amounts of state, prefer human-readable query parameters.

For larger payloads:

  • Store the payload in the URL fragment so that it is not sent to the server.
  • Serialize the payload, compress it with a suitable method, then encode the result as Base64url.
  • Debounce frequent URL updates and use history.replaceState() to avoid creating a history entry for every change.
  • If the compression format may change, identify it in the fragment so that existing links remain decodable.