DEV Community

Cover image for LaTeX in the Fragment Hash: How Simon Willison Built a Stateless Document Renderer That Stores Everything in the URL
mech.app
mech.app

Posted on Originally published at mech.app

LaTeX in the Fragment Hash: How Simon Willison Built a Stateless Document Renderer That Stores Everything in the URL

Simon Willison just shipped a LaTeX renderer that stores the entire document in the URL fragment. No server. No database. No session state. Every rendered document is a shareable link that carries its own source code.

The tool was inspired by Paul Ford's Tiny Little Office, which uses the same pattern for ten different document types. What makes this interesting for agent infrastructure is the llms.txt file that tells agents how to construct URLs programmatically. The result is a deployment pattern where agents can generate documents that are instantly shareable without touching a backend.

Why Fragment-Hash State Storage Matters

Traditional document rendering requires a persistence layer. You store the source in a database, generate a short ID, and serve the rendered output on request. This creates operational overhead: database migrations, backup strategies, rate limiting, authentication, and eventual consistency problems.

Fragment-hash storage inverts the model. The URL is the database. The browser is the renderer. The only server-side component is static file hosting.

What you gain:

  • Zero backend infrastructure for document persistence
  • Instant shareability without write operations
  • No user accounts or session management
  • Perfect cache-ability (static HTML + CDN)
  • No database schema migrations when you change rendering logic

What you lose:

  • URL length limits (typically 2,000 characters for safe cross-browser support)
  • No server-side analytics on document usage
  • No edit history or versioning
  • No access control (every URL is public)

For agent-generated documents, this trade-off often makes sense. Agents produce output that needs to be shared once, not iterated on collaboratively. The URL becomes the artifact.

Architecture: Compression, Rendering, and Sandboxing

The tool uses four CDN libraries:

  • Pako 2.1.0 for gzip compression
  • MathJax 4.1.3 for equation rendering
  • faster-latex 0.3.1 for full document typesetting
  • DOMPurify 3.4.16 for sanitization

The flow is simple:

  1. User pastes LaTeX source into an editor
  2. Client-side JavaScript compresses the source with Pako
  3. Compressed bytes are base64-encoded and written to window.location.hash
  4. On page load, the fragment is decoded, decompressed, and rendered
  5. Rendered output is injected into an <iframe sandbox> to prevent XSS

The compression step is critical. LaTeX is verbose. A simple equation like \frac{a}{b} is already 11 characters. A full document with preamble, sections, and bibliography can easily exceed URL length limits.

Pako uses a custom compression dictionary optimized for LaTeX syntax. Common tokens like \begin{, \end{, \frac{, and \documentclass compress aggressively because they appear repeatedly. The dictionary is built from a corpus of real LaTeX documents, so domain-specific syntax gets better compression than generic gzip.

Code: Fragment-Hash State Persistence

Here's the core pattern for writing state to the URL fragment:

function saveToFragment(latexSource) {
  const compressed = pako.gzip(latexSource);
  const base64 = btoa(String.fromCharCode(...compressed));
  window.location.hash = `#c:${base64}`;
}

function loadFromFragment() {
  const hash = window.location.hash.slice(1);
  if (!hash.startsWith('c:')) {
    return hash; // uncompressed fallback
  }
  const base64 = hash.slice(2);
  const compressed = Uint8Array.from(atob(base64), c => c.charCodeAt(0));
  return pako.ungzip(compressed, { to: 'string' });
}
Enter fullscreen mode Exit fullscreen mode

The c: prefix signals compressed content. Without it, the fragment is treated as raw LaTeX. This gives you a migration path: old links without compression still work, but new links benefit from smaller URLs.

The iframe sandbox is the XSS boundary:

<iframe sandbox="allow-scripts" srcdoc="...rendered HTML..."></iframe>
Enter fullscreen mode Exit fullscreen mode

The allow-scripts permission lets MathJax and faster-latex execute, but the iframe cannot access parent window state, cookies, or localStorage. User-supplied LaTeX is sanitized with DOMPurify before injection, but the sandbox is the hard boundary.

Agent Integration: The llms.txt File

Tiny Little Office includes an llms.txt file that documents the URL schema for agents. Willison copied this pattern. The file tells agents how to construct valid URLs without trial and error.

Example from the LaTeX tool:

# LaTeX Renderer URL Schema

Base URL: https://tools.simonwillison.net/latex/

## Uncompressed LaTeX
Append LaTeX source directly to fragment:
https://tools.simonwillison.net/latex/#\frac{a}{b}

## Compressed LaTeX
Prefix with c: and base64-encoded gzip:
https://tools.simonwillison.net/latex/#c:H4sIAAAAAAAA...

## Load from URL
Use ?url= parameter:
https://tools.simonwillison.net/latex/?url=https://example.com/doc.tex
Enter fullscreen mode Exit fullscreen mode

This is agent-friendly documentation. No natural language explanation. Just the schema, the prefix convention, and example URLs. An agent can parse this file and generate valid links without hitting the tool first.

Claude used this file to generate a working link to a Tiny Little Office presentation that explains the URL format. The agent read the schema, constructed the URL, and verified the output without human intervention.

Deployment Shape and Failure Modes

The deployment is a single HTML file with inline JavaScript. No build step. No server-side rendering. No API endpoints. You can host it on GitHub Pages, Netlify, Cloudflare Pages, or any static file CDN.

Failure modes:

Failure Impact Mitigation
URL exceeds 2,000 characters Link breaks in some browsers Compression dictionary tuning, warn user before copy
Malicious LaTeX in fragment XSS or resource exhaustion DOMPurify sanitization + iframe sandbox
CDN library unavailable Rendering fails entirely Subresource Integrity (SRI) hashes + fallback URLs
Browser blocks fragment navigation State loss on reload Use replaceState() instead of location.hash
Compression dictionary mismatch Decompression fails Version prefix in fragment (e.g., c1:, c2:)

The biggest operational risk is CDN availability. If MathJax or faster-latex goes offline, the tool stops working. Subresource Integrity hashes prevent tampering but do not solve availability. The mitigation is to host copies of the libraries yourself or use multiple CDN fallbacks.

The second risk is URL length. Compression helps, but a complex document can still exceed browser limits. The tool should warn users before copying a URL that might break. A fallback is to support Gist or Pastebin URLs as an escape hatch.

Observability and State Management

There is no server-side observability. You cannot track how many documents are rendered, which LaTeX packages are most common, or where errors occur. The browser console is the only log.

For agent-generated documents, this is often acceptable. The agent constructs the URL, the user clicks it, and the document renders. If rendering fails, the user reports the broken link and you debug locally.

If you need analytics, you can inject a client-side tracker (Plausible, Fathom, or a custom beacon). But this breaks the stateless model. Every page load now sends a network request, which means you need to handle rate limiting and privacy compliance.

State management is purely client-side. The editor uses localStorage to persist drafts between sessions, but this is optional. The URL fragment is the source of truth. If you clear localStorage, you lose unsaved edits, but shared links still work.

When to Use Fragment-Hash State

This pattern works when:

  • Documents are small enough to fit in a URL (after compression)
  • Shareability matters more than edit history
  • You want zero backend infrastructure
  • Agents generate output that does not need server-side validation
  • Users trust the rendering environment (no sensitive data in LaTeX)

Avoid this pattern when:

  • Documents exceed URL length limits (use Gist or Pastebin instead)
  • You need access control or user authentication
  • Server-side analytics are required for compliance
  • Collaborative editing or version history is expected
  • LaTeX source contains secrets (API keys, credentials)

For agent workflows, fragment-hash state is a good fit for ephemeral outputs: generated reports, formatted equations, one-off visualizations. It is a poor fit for long-lived documents that need to be updated or shared selectively.

Technical Verdict

Fragment-hash state storage is a deployment primitive that eliminates backend infrastructure for shareable documents. The trade-off is URL length limits and no server-side observability. For agent-generated LaTeX, this is a reasonable trade.

The compression strategy (Pako with a custom dictionary) is the key to making this work. Without it, most documents would exceed URL limits. The iframe sandbox is the security boundary that prevents XSS without server-side validation.

The llms.txt file is the agent integration point. It documents the URL schema in a machine-readable format, so agents can construct valid links without trial and error. This is a pattern worth copying for other stateless tools.

Use this when you need instant shareability without backend complexity. Avoid it when you need access control, analytics, or documents larger than 2,000 characters (compressed).

Source Links

Top comments (0)