kage — Clone Any Website for Offline Reading Without JavaScript
kage — Clone Any Website for Offline Reading Without JavaScript
You hit "Save As" on a page you want to keep, and six months later you open it to find a blank screen, a spinner that never stops, or a copy that still tries to phone home to an analytics server that no longer exists. The page was never really yours — it was a thin client for someone else's JavaScript.
kage (影, "shadow") takes the other road. It drives a real headless Chrome browser, lets the page finish rendering, snapshots the DOM a human would have seen, then rips every script out of it. What lands on disk looks like the live site and runs no code. No tracking, no network calls, no surprises.
Created by Tam Nguyen Duc and written in Go, kage has gathered over 2,700 GitHub stars since its release in June 2026. It's the tool you reach for when you want to archive a site you love, read documentation on a plane, or freeze a piece of the web before it changes forever.
Why It's Trending
- The web is falling apart — 90% of modern sites require JavaScript to render anything. "Save As" saves a loader shell, not the content. kage snaps your browser's finished view.
- Zero-JavaScript mirrors — every script tag, event handler, and
javascript:URL is stripped. The output is pure HTML + CSS + images that opens straight off disk. - Open standard archives (ZIM) — pack a mirror into a single
.zimfile readable by Kiwix and any ZIM-compatible reader. You're not locked into kage. - Self-contained binaries — pack a mirror into a single executable that is the site. Send it to someone who needs nothing installed.
- Docker-ready — official container with bundled Chromium, no host setup required.
- Polite crawler — respects
robots.txt, readssitemap.xml, uses breadth-first crawl with configurable concurrency.
Prerequisites
- Go 1.22+ (to build from source, or use a prebuilt binary)
- Chrome or Chromium on the host (kage finds it automatically; use
--chrometo point elsewhere) - OR Docker — the container image bundles Chromium
Installation
Prebuilt Binary (macOS, Linux, Windows)
Grab the latest release from GitHub releases. Archives, .deb, .rpm, and .apk packages are available.
Go Install
go install github.com/tamnd/kage/cmd/kage@latest
Homebrew (macOS)
brew install --cask tamnd/tap/kage
Docker
docker pull ghcr.io/tamnd/kage
Linux Package Managers
Debian / Ubuntu:
curl -fsSL https://tamnd.github.io/linux-repo/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/tamnd.gpg
echo "deb [signed-by=/usr/share/keyrings/tamnd.gpg] https://tamnd.github.io/linux-repo/apt stable main" | sudo tee /etc/apt/sources.list.d/tamnd.list
sudo apt update && sudo apt install kage
Fedora / RHEL:
sudo dnf config-manager --add-repo https://tamnd.github.io/linux-repo/dnf/tamnd.repo
sudo dnf install kage
Quick Start
Clone a website and serve it offline — two commands, ten seconds:
# 1. Clone Paul Graham's essays into ~/data/kage/paulgraham.com/
kage clone paulgraham.com
# 2. Serve it on http://127.0.0.1:8800
kage serve ~/data/kage/paulgraham.com
Open http://127.0.0.1:8800 in your browser. Every essay, every image, every stylesheet — frozen on your disk and running with zero network.
Clone Options
kage gives you fine-grained control over the crawl:
| Flag | Default | Purpose |
|---|---|---|
--max-pages / -p |
0 (unlimited) | Stop after N pages |
--max-depth / -d |
0 (unlimited) | How many links deep to follow |
--scope-prefix |
Only crawl paths under this prefix | |
--subdomains |
false | Include subdomains of the seed host |
--scroll |
false | Auto-scroll to trigger lazy-loaded images |
--workers |
4 | Concurrent page renders |
--refresh |
false | Re-render existing pages in place |
--exclude |
Path prefixes to skip (repeatable) |
Examples:
# Quick snapshot — 50 pages, 2 links deep
kage clone paulgraham.com --max-pages 50 --max-depth 2
# Whole section of a documentation site
kage clone go.dev --scope-prefix /doc
# Large site with subdomains, lazy-loaded content
kage clone example.com --subdomains --scroll --workers 8
# Catch up: re-render pages that have changed
kage clone paulgraham.com --refresh
Packing for Sharing
A mirror is a folder — great for browsing, lousy for moving around. Use kage pack to collapse it into a single artifact:
ZIM archive (recommended for portability):
kage pack paulgraham.com
# Creates paulgraham.com.zim
kage open paulgraham.com.zim
ZIM is an open standard used by Kiwix, Wikipedia offline, and Project Gutenberg. Your archive works with any ZIM reader today and years from now.
Self-contained binary:
kage pack paulgraham.com --format binary -o paulgraham
./paulgraham
A single executable that serves the site. Whoever you send it to needs nothing installed — not kage, not a ZIM reader, nothing.
Desktop app (double-click ready):
# macOS
kage pack paulgraham.com --app
open paulgraham.app
# Linux (AppImage)
kage pack paulgraham.com --app --base kage-linux-amd64
Architecture
kage's pipeline is divided into five stages:
-
Crawl — The seed URL feeds into a breadth-first crawl frontier that respects
robots.txt, readssitemap.xml, and stays on the seed host. Each discovered URL is normalized and deduplicated — the same page reached via HTTP vs HTTPS or with/without trailing slash is fetched exactly once. -
Render — A pool of headless Chrome tabs (default: 4 workers) renders each page and waits for it to settle. The
--scrollflag triggers lazy-loaded images. kage takes a snapshot of the DOM a human would have seen — not the raw HTML but the post-JavaScript rendered state. -
Sanitize — Every
<script>tag, inline event handler (onclick,onload, etc.), andjavascript:URL is stripped from the snapshot. The result is pure HTML that can never make a network call or execute code. -
Localize — CSS, images, and fonts are downloaded via a separate HTTP asset pool. All URLs in the HTML are rewritten to local paths. The output structure mirrors the original site:
paulgraham.com/index.html,paulgraham.com/greatwork.html,paulgraham.com/_kage/for assets. -
Pack — The mirror on disk can be collapsed into a ZIM archive (zstd-compressed, open standard), a self-contained binary (kage + archive appended), or a double-click desktop app (
.appon macOS, AppImage on Linux).
Docker Usage
Skip installing Chrome entirely. The official container bundles Chromium:
docker run --rm -v "$PWD/out:/out" ghcr.io/tamnd/kage clone paulgraham.com
The mirror lands in ./out/paulgraham.com/ on your host. Serve it:
docker run --rm -v "$PWD/out:/out" -p 8800:8800 ghcr.io/tamnd/kage serve /out/paulgraham.com
kage serve and kage open
kage serve <mirror-dir>— runs a tiny static file server on port 8800. Links and assets resolve exactly as they would on the live host.kage open <file.zim>— serves a ZIM archive through the same HTTP interface.- Native window — build with
-tags webviewand kage opens in a native OS window (WKWebView on macOS, WebView2 on Windows, WebKitGTK on Linux) instead of a browser tab.
Use Cases
- Flight mode reading — clone a blog or documentation site before a trip. Read offline at 35,000 feet.
- Documentation archiving — freeze a version of your project's docs before a major redesign.
- Research preservation — capture articles, papers, and references before they disappear behind paywalls or get taken down.
- Historical snapshots — keep a personal archive of sites you care about, viewable exactly as they were.
- Knowledge sharing — pack a ZIM or binary and share it with colleagues who need offline access.
Resources
- GitHub: github.com/tamnd/kage
- Documentation: kage.tamnd.com
- Releases: github.com/tamnd/kage/releases
- Docker Image: ghcr.io/tamnd/kage
- License: MIT