Open-source project
tamnd/kage avatar
tamnd/kage

kage: Clone a Website into Script-Free HTML with Headless Chrome

Shadow any website for offline viewing, with the JavaScript stripped out

3,401 stars130 forksGoMIT

At a glance

What is it?
kage is a Go CLI that renders pages in real headless Chrome, strips every script, and writes a browsable offline mirror you can pack into a single ZIM file or a self-serving binary.
Who is it for?
Adopt kage if you need frozen, script-free copies of documentation, essays or reference sites for offline reading, and you accept that headless Chrome and a full page render are required for every page. Do not adopt it if you need to archive interactive applications or log in behind authentication; the crawl is read-only and the output runs no code by design.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 39 days ago.
What is it written in?
Mainly Go, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 17, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What problem kage solves, and who it is for

Browser Save As produces a page that still depends on remote scripts, analytics endpoints and CDNs that may not exist later. The README describes the failure directly: a saved page that opens to a blank screen or a spinner because the site was a thin client for someone else's JavaScript. kage takes the opposite route. It loads the page in real headless Chrome, waits for it to settle, snapshots the DOM a human would have seen, then deletes all the JavaScript and pulls CSS, images and fonts down to local paths.

The audience is narrow and specific. People who want to keep documentation, essays or reference material readable for years without a network. People who want to hand a site to someone else as a single file. People who want to inspect what a page actually rendered without executing its tracking code. It is not a general web archiver for interactive applications, and it is not a scraping framework; the output is a folder of .html files, not a dataset.

How the crawl works: headless Chrome, BFS, and idempotent page keys

kage is a breadth-first crawler driven by a real browser. The go.mod lists github.com/go-rod/rod, so the rendering layer is the Rod browser automation library, and the Dockerfile bundles Chromium with KAGE_CHROME pointing kage at the system binary so it never tries to download its own. Chrome or Chromium must exist on the host; kage finds a system install on its own, or you point it somewhere specific with --chrome or the KAGE_CHROME environment variable.

The crawl is polite by default. It reads robots.txt, seeds itself from sitemap.xml, and stays on the seed host unless --subdomains is set. Each page is keyed by the file it writes, so the same essay reached over http and https, with or without a trailing slash, is fetched exactly once. Ctrl-C saves the crawl position; running the command again resumes. --refresh re-renders in place, --force wipes the host directory and starts clean. The --workers flag defaults to 4 and controls how many pages render at once, which is the knob that matters when a site is large or the machine is small.

The repository layout shows the pipeline split into packages: clone/ for the crawler, browser/ for the Rod layer, sanitize/ for script removal, pack/ and zim/ for archiving, viewer/ for the native window, and robots/ for the rules parser. That separation is the clearest evidence of how the tool is built, and it also tells you where to look if a page renders incorrectly.

Installing kage and mirroring a site for offline reading

The README gives a Go install as the primary path, and prebuilt binaries, .deb, .rpm and .apk packages as alternatives. Homebrew, Scoop, apt and dnf are also documented. If you would rather not install Chrome yourself, the container image bundles Chromium.

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

After that, the quick start is two commands. The first clones a site into $HOME/data/kage/<host>/ by default, and the README uses paulgraham.com as the example.

bash
kage clone paulgraham.com

The second serves the mirror over a local HTTP server on 127.0.0.1:8800 so you can read it back in a browser with no network involved.

bash
kage serve $HOME/data/kage/paulgraham.com

For a bounded first run, the README shows limiting the crawl to 50 pages two links deep, or restricting it to a single path prefix with --scope-prefix. Both are worth using before you point kage at a large site, because --max-pages counts pages that fail, are disallowed, or are not HTML, not just the pages that land in the mirror.

bash
kage clone paulgraham.com --max-pages 50 --max-depth 2
kage clone go.dev --scope-prefix /doc

If you want a single shareable artifact rather than a folder, kage pack collapses the mirror into a ZIM archive and kage open serves it back. The --format binary variant produces an executable that serves the site itself and needs nothing installed.

bash
kage pack paulgraham.com
kage open paulgraham.com.zim
kage pack paulgraham.com --format binary -o paulgraham

Where kage fails, and when it is the wrong tool

The design has a hard boundary: the mirror runs no code. Any page that depends on JavaScript to display content after load, to paginate, or to fetch data on interaction will be captured in whatever state the DOM reached before the script was stripped. The --scroll flag exists precisely because lazy-loaded images need the page to be scrolled before the snapshot is taken, which is an admission that a page's rendered state depends on how it was driven.

The second constraint is that rendering is expensive. Every page needs a real Chrome instance and a wait for the page to settle, and the default --workers value is 4. A large site mirrored with default settings will take a long time, and the README does not document a way to skip rendering for pages you know are static. The --no-robots flag exists but the README's own comment on it is to be nice, which is the right framing: ignoring robots.txt is a choice with consequences for the site you are hitting.

The container image runs as root on purpose. The Dockerfile comment explains that a bind-mounted /out is owned by whoever created it on the host, so only root can reliably write into it, and that an unwritable HOME also breaks Chrome's crash handler. If your environment forbids root containers, the container path is not for you, and you should install the binary and Chrome directly instead.

Finally, kage is a read-only public crawler. There is no documented authentication, no cookie import and no login flow, so anything behind a session is out of scope.

kage compared with wget mirroring and single-file save tools

The obvious alternative is wget --mirror or httrack. Those tools fetch HTML over HTTP and rewrite links, which is fast and needs no browser. The difference in approach is the starting point: wget sees the server's initial response, while kage sees what Chrome rendered after scripts ran. For a static documentation site the two produce similar results and wget is far cheaper. For a site that builds its navigation or content client-side, wget captures an empty shell and kage captures the finished DOM.

A second alternative is the single-file save approach, where a browser extension or tool inlines a page's assets into one HTML document. That gives you one file per page rather than a folder tree, which is convenient for sharing a single article, but it does not crawl a site, does not follow links, and does not produce a browsable mirror. kage's pack command is the middle ground: it keeps the crawl but collapses the result into one ZIM archive or one executable.

The trade-off is consistent across both comparisons. kage buys fidelity to the rendered page at the cost of a browser dependency and a slower crawl. If your target site is plain HTML and will stay that way, the simpler tools win on speed and on install footprint.

Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-08-10. The most recent release, v0.3.12, is dated 2026-08-10, with v0.3.11 on 2026-08-01 and v0.3.10 on 2026-07-11. Release cadence over that window is roughly one release per month, and the version numbers are still in the 0.3.x range, so the command surface should be treated as movable.

Upgrade cost is mostly about Chrome, not kage. Because kage drives a real browser, a Chrome update that changes rendering behaviour can change what your mirrors contain, even if the kage binary is unchanged. The Makefile exposes a test-short target that skips the tests launching a real browser, which implies the full suite depends on Chrome being present. If you pin kage in CI, pin a Chrome version alongside it.

The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is a permissive licence, but the mirrors you produce are a separate question: kage does not grant you rights to the content it copies, and robots.txt compliance is a crawl-time setting, not a licensing one. Nothing here is legal advice; check the terms of the site you intend to mirror.

Editorial conclusion

Adopt kage if you need frozen, script-free copies of documentation, essays or reference sites for offline reading, and you accept that headless Chrome and a full page render are required for every page. Do not adopt it if you need to archive interactive applications or log in behind authentication; the crawl is read-only and the output runs no code by design. Before relying on it, verify that Chrome or Chromium is found on the host (or pass --chrome), and check the --max-pages, --max-depth and --scope-prefix defaults against the size of the site you intend to mirror.

Frequently asked questions

What is kage by tamnd?

It is a Go command-line tool that clones a website into a folder you can browse offline, with every script stripped out. It opens each page in real headless Chrome, waits for the page to settle, snapshots the DOM, then deletes all JavaScript and rewrites CSS, images and fonts to local paths.

How do I install kage?

The README gives go install github.com/tamnd/kage/cmd/kage@latest as the primary path. Prebuilt archives, .deb, .rpm and .apk packages are published on the releases page, and Homebrew, Scoop, apt and dnf are also documented.

Does kage need Chrome or Chromium installed?

Yes. kage drives a real browser, so it needs Chrome or Chromium on the host. It finds a system install on its own, or you can point it somewhere specific with the --chrome flag or the KAGE_CHROME environment variable. The container image bundles Chromium and needs nothing extra.

Can kage clone a site that requires a login?

The README does not document authentication, cookie import or a login flow, so anything behind a session is out of scope. The crawl is read-only and stays on the seed host unless --subdomains is set.

How do I pack a kage mirror into one file?

Run kage pack on the mirror directory to collapse it into a ZIM archive, then kage open serves that archive back for offline reading. The --format binary option produces an executable that serves the site itself and needs nothing installed.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. tamnd/kage on GitHub
Community notes

Community notes