🔥 Fragment-Hash State Storage: Shareable Docs in 2026
Discover fragment-hash state storage for stateless document renderer apps: client-side document rendering with compression + sandboxing—share agent-generated docs via URL fragment storage.

Put the Document in the Link: A Stateless Rendering Pattern for AI Tools
A document link usually points to something stored somewhere else. A server, database, or file service holds the source, and the link carries an identifier you can use to fetch it. That model is familiar - but it also means you end up running storage and retrieval infrastructure for every shared artifact.
A different approach puts the document's state directly into the URL fragment. The browser reads that state, decompresses it, and renders the document locally. There's no document database or write API in the critical path. Simon Willison's LaTeX renderer is a good example, and its llms.txt file shows how an agent can learn to construct working links directly.
This isn't meant to replace a document platform. It's a deployment choice for small, public, self-contained artifacts where quick sharing matters more than permissions, history, or collaboration. This article explains how the pattern works, what engineering details you need to scrutinize, and a practical checklist for deciding whether to use it.
What fragment-hash storage changes
In a conventional web app, a user creates a document and the application saves it. A database stores the source (or rendered output), a server assigns an ID, and later requests retrieve that record. That structure makes sense when documents need to persist, support permissions, or be edited over time - and it also creates operational work: storage, backups, migrations, authentication, abuse controls, and failure handling.
Fragment-hash storage flips the arrangement. The URL fragment - the part after # - contains the document state. The browser reads that fragment and renders the document from a static page. In the LaTeX renderer example, the page is hosted as a static asset while JavaScript handles decoding and rendering locally. The URL becomes the artifact, not a pointer to a database record.
What you gain - and what you give up
The practical benefits are pretty direct:
- No persistence backend: You don't need a database or API just to save and retrieve small documents.
- Simple sharing: A user can copy a link that includes everything needed to render the artifact.
- Low operational overhead: Static hosting can run on a CDN without maintaining application servers.
- No document schema migration: You can change the renderer without migrating stored records, though you still need compatibility for old links.
- A natural fit for generated output: An AI agent can produce a result and return a URL, without making a separate write request.
The trade-offs matter, too. A URL is not private storage, and a fragment doesn't provide access control. There's no built-in edit history, collaborative workflow, or reliable server-side record of who opened what. The browser address bar also has length limits, and those limits vary by browser, app, messaging tool, and infrastructure. There's no universal "2,000 characters is safe everywhere" rule you can trust.
Also, a fragment typically isn't sent in the HTTP request to the hosting server. That can reduce what the server sees, but it doesn't make the content secret. The full link can show up in browser history, get pasted into chat, or appear in screenshots. Treat any shareable fragment as public data.
The right mental model
Think of this as a portable, client-rendered artifact, not as a database record with a special interface. It works best when the document is small, self-contained, and meant to be shared "as is." It's a bad fit when the document needs controlled access, central deletion, audit logs, or real collaboration over time.
That distinction helps you avoid a common mistake: choosing a stateless design because it sounds simpler, then trying to bolt persistence and permissions on later. The question isn't "Can the URL hold the document?" It's "Does this document need to behave like a record, or like a shareable snapshot?"
How the renderer works - and where the risks are
The basic flow is straightforward. A user enters LaTeX, the browser compresses it, and the result gets encoded into the fragment. On page load, client-side code reads the fragment, reverses the encoding and compression, and then sends the source to a renderer. The output is displayed in an iframe sandbox.
A simplified version of the state-handling pattern looks like this:
function saveToFragment(source) {
const compressed = pako.gzip(source);
const binary = String.fromCharCode(...compressed);
const base64 = btoa(binary);
window.location.hash = `c:${base64}`;
}
function loadFromFragment() {
const hash = window.location.hash.slice(1);
if (!hash.startsWith('c:')) {
return hash; // Optional legacy, uncompressed format
}
const binary = atob(hash.slice(2));
const bytes = Uint8Array.from(binary, char => char.charCodeAt(0));
return pako.ungzip(bytes, { to: 'string' });
}
This is illustrative, not production-ready code. It skips URL-safe encoding, error handling, size checks, and compatibility details. For large byte arrays, spreading values into String.fromCharCode can also exceed argument limits. Use a tested conversion routine rather than assuming a small demo will work for real documents.
Compression and URL compatibility
Compression helps because formats like LaTeX contain repeated commands and structure. But compression doesn't remove URL length limits, and Base64 usually expands binary data by about a third. Measure the final encoded URL, not just the compressed byte count.
One technical detail is also worth verifying before you copy implementation claims: standard gzip doesn't include a portable preset-dictionary mechanism. If your design depends on a custom compression dictionary, check the actual compression format, library options, and decoder behavior. A custom dictionary can improve compression when the format supports it, but the encoder and decoder have to agree on the settings. Don't assume that "call gzip" automatically gives you a shared dictionary behavior.
For links that need to move across systems, consider URL-safe encoding or encoding the fragment correctly. Standard Base64 may include +, /, and =; those characters can work in fragments, but they make copying and interoperability trickier. A versioned prefix like c1: is a practical start: it lets the decoder distinguish formats and gives you room to change encoding later.
Before copying or sharing a generated URL, the interface should:
- Measure the complete URL and warn when it's getting long.
- Catch malformed Base64 and decompression errors without crashing the page.
- Enforce a maximum input size so decoding doesn't chew up browser resources.
- Keep support for older link formats only if you truly need it.
- Test links through the browsers and messaging tools your users actually use.
Rendering is a security boundary
A document renderer takes user-controlled input. LaTeX isn't automatically safe just because it's a typesetting language. Depending on the rendering engine and enabled features, crafted input can trigger unwanted HTML, scripts, network requests, or expensive computation.
This example architecture pairs output sanitization with an iframe sandbox. Those controls address different risks. A sanitizer like DOMPurify can remove or constrain unsafe markup. An iframe with a restrictive sandbox attribute adds an isolation boundary around the rendered output. For example, sandbox="allow-scripts" lets scripts run inside the frame, but withholds other capabilities unless you explicitly grant them. That's a real restriction - but it still needs testing against what your renderer actually supports.
Use the least permissive sandbox that works. Avoid allow-same-origin unless you have a specific, reviewed reason. Confirm the frame can't read parent-page state, cookies, or local storage, and think about whether the renderer needs any network access. Test malicious and malformed inputs, not only ordinary equations. A sandbox isn't a substitute for input limits, safe rendering, or dependency maintenance.
Making the pattern usable by AI agents
A stateless renderer becomes much more useful when an agent can generate a valid link without a human clicking through a UI. That means you need a clear, machine-readable contract: base URL, fragment format, encoding rules, version prefixes, and any alternative input-loading methods.
The LaTeX tool's llms.txt file is a concrete example. It lays out the URL schema and shows examples of compressed and uncompressed forms. The key point isn't that the agent needs some special protocol. It needs an unambiguous description of how the existing tool accepts input.
A useful agent-facing specification should answer:
- What is the canonical tool URL?
- How is the document text encoded and compressed?
- What prefix or version identifies the format?
- Are raw fragments supported, and how should special characters be escaped?
- Is there a size limit or an external-source option?
- How can the agent confirm the generated link is valid?
- What should it do when the document exceeds the limit?
Keep the spec factual and concise. A model shouldn't have to guess whether #c: means compressed content, whether compression is gzip or something else, or whether the browser will fetch an external URL. Ambiguity produces links that look plausible but fail at render time.
Why this is useful for agent workflows
Traditional agent integrations often require an API key, a write endpoint, and a server-side record before the agent can produce something shareable. With fragment state, the agent can construct a URL from documented rules and return it immediately. That works well for one-off equations, short reports, or generated presentations that don't need collaborative editing.
Still, separate link construction from link validation. An agent can follow the schema and output a URL, but that doesn't prove the document renders correctly in every browser you care about. If reliability matters, add a lightweight validation path: render the generated URL in a browser test, or have the tool return a clear error state when decoding or typesetting fails.
Useful starting points include Simon Willison's LaTeX renderer, the Tiny Little Office project that popularized the pattern, and the source repository for implementation details. Check the current implementation and docs before you copy anything: the URL schema is part of the tool's public interface and can change.
When to use it and how to ship it safely
This pattern works best when documents are small, shareable, and basically disposable. Examples include a generated equation, a compact diagram, or a one-off formatted report. It's less suitable for contracts, private business documents, or anything that needs a dependable revision history.
Use fragment-based state when:
- The output is small enough to fit comfortably in a URL after encoding.
- A shareable snapshot matters more than collaborative editing.
- The document doesn't include secrets or sensitive personal information.
- You don't need server-side permissions, deletion, or audit trails.
- Static hosting and low operational overhead are meaningful benefits.
Use a database, object store, or document service instead when you need authentication, access revocation, versioning, search, collaboration, compliance logging, or predictable handling for large files. A hosted paste or gist service can be an escape hatch for oversized content, but it adds dependencies and its own privacy/retention considerations. It isn't automatically safer than fragment links.
A practical deployment checklist
You can deploy a single static page on services like GitHub Pages, Netlify, or Cloudflare Pages. Even if deployment is easy, browser-side failure modes still deserve attention.
- Define and version the format. Specify the encoding and compression format, and use a versioned prefix if you expect changes over time.
- Set input and output limits. Reject unusually large source and warn before copying a long link. Base limits on real browser and sharing-channel tests, not a universal character count.
- Handle decode failures. Corrupt fragments, truncated links, and unsupported versions should show a useful message and a recovery path.
- Review the rendering boundary. Sanitize rendered output, restrict the iframe sandbox, and test resource-intensive input.
- Manage dependencies. Pin library versions and consider Subresource Integrity (SRI) for CDN-hosted scripts. SRI can detect unexpected content; it can't make an unavailable CDN available. Self-host critical libraries or plan a tested fallback.
- Decide what observability means. Static hosting won't tell you which documents failed to render. Browser-side analytics can help, but it adds network requests, privacy obligations, and operational complexity. Don't add tracking by default if you're trying to keep things stateless.
- Test old links. If shared URLs are part of the product, preserve decoder compatibility intentionally, or document when older links will stop working.
The biggest operational risk isn't "no database." It's the assumptions you push onto the client: encoding stays compatible, dependencies load, links survive copying, and untrusted input stays contained. Those risks are manageable, but they don't vanish just because there's no backend.
Conclusion
Putting document state in a URL can remove a surprising amount of infrastructure. For small, public snapshots, it's genuinely convenient: an agent generates a document, creates a link, and a static page renders it without a database write path.
But the trade-off is real. URLs aren't private storage, large documents are fragile, and client-side rendering shifts responsibility toward encoding, dependency availability, and browser security. Treat the fragment as a versioned data format, test the full sharing path, and use a persistent service when the document needs permissions or history.
If you're building an agent tool, start with one narrow output type and document its URL schema clearly. Then test size limits, malformed input, and old links before you expand the pattern. Pick the smallest architecture that matches the artifact's real lifecycle - rather than the one that just looks "stateless."