Snapshot.jl
How it works

A Pluto notebook becomes a Therapy component

Snapshot takes a Pluto.jl notebook and emits a lean Therapy component — supported interactive sliders and plots run in the browser as WebAssembly, with no Julia server, no Pluto frontend, and no multi-megabyte statefile. It drops into any static host as a standalone page, or into a Therapy app as native DOM — themeable, with or without reactivity.

The problem it solves

Pluto's classic static export is faithful but heavy: it bakes the entire session state (~2.6 MB) into the HTML and ships the full Pluto frontend to re-hydrate it. For a page of sliders and a plot, that's a lot of bytes — and it locks the look to Pluto's chrome.

The lean export keeps rendered notebook content and verified interactions, while omitting Pluto's editor and session machinery. For notebooks whose interactions compile, this can substantially reduce frontend and session-state overhead.

The pipeline

At export time the notebook runs once in Pluto. Then, per notebook:

1

Extract the reactive graph

Pluto already knows the dependency graph + which cells depend on a @bind widget. Snapshot groups co-dependent bond cells and lifts each dependent cell into a pure function.

2

Compile to WebAssembly islands

Each bond group compiles to a small WasmGC module via WasmTarget.jl — verified at export time against real notebook re-runs before it ships.

3

Server-render every cell

Each cell's output is written straight into the page as HTML, following Pluto's own decisions about ordering, output, and visibility. Nothing structural is invented.

4

Hydrate with signals + islands

A tiny runtime wires each <bond> input to the wasm island that owns it. Move a slider and only the dependent cells recompute — figures redraw on a live <canvas>, numbers update in place.

Why it maps cleanly

Pluto's reactive model and Therapy's signals model are the same idea, so the translation is direct:

@bind x Slider(…)
signal + <input>
a shared reactive value the widget writes + cells read
bond-dependent cell
island / effect
wasm recomputes it when its inputs change
static cell
SSR HTML
rendered once at export, no runtime
figure output
live <canvas>
redrawn by WasmMakie, not a baked image
text / number
reactive DOM node
updated in place — divs, not canvas

Theming: copy Pluto, swap the variables

The export ports Pluto's own output CSS almost verbatim — markdown, admonitions, tables, code, the tree viewer, the table of contents — and re-points every Pluto color variable at a DaisyUI token.

So the notebook renders exactly as Pluto does, but one attribute — data-theme="…" on <html> — restyles the whole site at once. The 🎨 picker in the nav sets it: because each notebook is native DOM (no iframe), the chrome and every notebook reskin together, seamlessly, with one click.

Plain native controls with no authored class or inline style inherit those theme tokens too. A class, inline style, or data-snapshot-unstyled leaves the control's appearance with the notebook author.

Two delivery formats

Portable HTML

Embed Snapshot's runtime and WASM modules in one file. Double-click it, copy it, or upload it as a single artifact.

export_notebook("notebook.jl";
    single_file=true)

Static directory

Keep the smaller HTML and its .islands directory together on GitHub Pages, S3, or any ordinary static host.

export_notebook("notebook.jl")

Direct opening and static hosting

The efficient directory export uses neighboring WebAssembly and JSON files, which browsers intentionally block when its HTML is opened through a file:// URL. Serve that directory through any static host.

For the simplest local handoff, pass single_file=true. Snapshot embeds its generated runtime assets into the document, so the resulting HTML can be opened directly without running a local server. This is convenient for downloads and platforms that accept active HTML, but base64 increases binary size, the browser cannot cache individual modules, and a host may still block scripts or WebAssembly. Use the split format for a deployed site.

For a Therapy site, use a small Snapshot build environment to export into the site's public directory, then a separate Therapy environment to mount it with staticfiles(app, "public/notebook", "notebook"). The separate environments are intentional because Snapshot's Pluto stack and Therapy currently resolve different HTTP major versions. julia --project=therapy-site therapy-site/app.jl dev serves it locally; julia --project=therapy-site therapy-site/app.jl build copies the complete mount into the site's static output and writes .nojekyll for GitHub Pages. Set Therapy's base_path="/REPOSITORY" for a project Pages URL. Snapshot is needed at build time only; the deployed site has no Julia server. Mount only trusted notebook HTML on an application's origin; publish untrusted exports on a separate origin.

Browser compatibility

Snapshot's current WasmTarget output uses WasmGC and WebAssembly JavaScript string builtins. Use Chrome or Edge 130+, Firefox 134+, or another browser with equivalent support. Safari does not currently implement the required string builtins. This is a browser-engine requirement, not an operating-system restriction.

Snapshot's export verifier is the bundled Node 24 JLL, never a Node executable from PATH. Export is currently available on 64-bit Windows; x86_64 and ARM64 macOS; x86_64 and ARM64 Linux with glibc or musl; and powerpc64le Linux with glibc. Generated HTML can be viewed on any operating system with a compatible browser.

A plain HTML slider can still move in an older browser even when the adjacent WebAssembly module cannot compile. If the slider moves but a plot or value stays fixed, update the browser first. Current exports surface the failed feature probe and its technical detail directly in the notebook.

Trust boundary

Exporting executes the notebook and its package environment with the permissions of the Julia process. Snapshot is an exporter, not a sandbox. Build only trusted notebooks locally; use a disposable, network-restricted runner without credentials for community-supplied code. Generated HTML is author-controlled content and should be served from an origin isolated from authenticated applications.

The package works on any static host. snapshot.show is a separate hosted service, not a runtime dependency of Snapshot.jl.

The classic export (not recommended)

Passing therapy=false explicitly selects the original full-Pluto export — Pluto's standard static HTML plus a JavaScript bundle that hydrates the @bind cells in Pluto's static export, alongside Snapshot's island assets. It still works:

export_notebook("notebook.jl"; therapy=false)
Not the recommended path: it ships the full Pluto frontend plus a baked ~2.6 MB statefile (much heavier), isn't a native Therapy component, and isn't as heavily tested. The default Therapy export is the supported path unless you specifically need the classic export.

See it in action

Every notebook in the gallery is a live Therapy component. Try the theme picker.

Browse the Notebooks →

Pluto notebooks as lean Therapy components — interactive WebAssembly, no server.