"Save As" has been lying to you for decades. You click it on a page you want to keep. Six months later you open it and find a blank screen, a spinner that never resolves, or a "live" copy still phoning home to analytics servers that have moved on without you. The page was never really yours — it was a thin client for someone else's JavaScript.

Kage takes the other road.

What It Is

Kage (影, "shadow") is a Go CLI that clones websites for offline reading the right way. It drives real headless Chrome, waits for the page to finish doing whatever it does, captures the final DOM a human would actually see, then strips every <script> tag out. CSS, images, and fonts come down to local paths. What lands on disk looks like the live site and runs no code.

No tracking. No network calls. No surprises. Just .html files you can open off disk, hand to a friend, or pack into a single file and forget about for a decade.

Project: github.com/tamnd/kage

Why It's Worth Your Time

Most offline archival tools fall into one of two failure modes: they either can't handle JS-rendered pages and save empty shells, or they save something that still depends on remote servers for half its assets. wget --mirror will happily clone a site and leave you with hundreds of pages that load nothing because the CSS was served from a CDN it didn't follow.

Kage's approach is different. It lets the browser do the full render — JavaScript, dynamic content, lazy images — and then amputates the scripting layer. The finished HTML stays. The code that generated it doesn't.

Two features stand out. First, kage pack can compress an entire mirror into a single self-executing binary. Run ./paulgraham on any machine and it serves itself. No dependencies, no installation required. Second, --scope-prefix keeps crawls surgical — point it at /doc/ and it won't spider the rest of the site. Most crawlers will happily index half the internet if you let them.

Hands On

Kage needs Chrome or Chromium on the host to drive the rendering. If you'd rather not install it, the Docker image bundles Chromium and needs nothing else:

# Docker — no local Chrome required
docker run --rm -v "$PWD/out:/out" ghcr.io/tamnd/kage clone paulgraham.com

Installing directly with Go:

go install github.com/tamnd/kage/cmd/kage@latest

Prebuilt .deb, .rpm, and static binaries are available on the releases page if you'd rather skip the Go toolchain.

Basic workflow:

# Clone a site (saves to ~/data/kage/paulgraham.com/ by default)
kage clone paulgraham.com

# Serve it back locally
kage serve ~/data/kage/paulgraham.com
# open http://127.0.0.1:8800

# Partial clone — first 50 pages, 2 links deep
kage clone paulgraham.com --max-pages 50 --max-depth 2

# Only a specific section of a site
kage clone go.dev --scope-prefix /doc

# Scroll pages to trigger lazy-loaded images
kage clone example.com --scroll

# Re-render in place to pick up new content
kage clone paulgraham.com --refresh

Packing for distribution:

# Single shareable ZIM archive
kage pack paulgraham.com
# → paulgraham.com.zim

# Self-contained executable that IS the site
kage pack paulgraham.com --format binary -o paulgraham
./paulgraham  # serves itself, needs nothing installed

The ZIM format is the same one used by Kiwix — a mature offline reading ecosystem with apps for Android, iOS, and desktop. Worth knowing if you want to browse the archive on mobile.

Honest Verdict

The Chrome dependency is the obvious friction point. On a headless server, apt install chromium works fine, but it's not nothing. The Docker image sidesteps this cleanly if containers are part of your workflow.

Auth-gated content is out of scope unless you can pre-authenticate a Chrome session and hand it off. Heavy SPAs with complex client-side routing will need tuning; --scroll helps with lazy loading but it can't work around apps that render entire routes in JavaScript with no server-side HTML.

For documentation sites, essays, technical blogs, reference material — anything static or close to it — this is exactly the right tool. The core idea is sound, the execution is clean, and the "self-executing binary" trick alone makes it worth bookmarking.

Go Try It

The Docker path is the lowest-friction start — no Chrome install, no Go toolchain, just a URL:

docker run --rm -v "$PWD/out:/out" ghcr.io/tamnd/kage clone <your-site-here>

Full documentation at kage.tamnd.com.