- JavaScript 57.3%
- Svelte 32.4%
- TypeScript 5.5%
- CSS 4.2%
- Dockerfile 0.3%
- Other 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| client | ||
| server | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
Amber
Preserve web pages — or whole sites — in high fidelity, with a UI you actually enjoy using.
Amber renders each page in a real Chromium, waits for it to settle, then freezes the live DOM exactly as displayed: CSS-in-JS rules that only exist in the CSSOM, open shadow roots, the image candidate the browser actually picked, lazy-loaded media, typed form values, canvas pixels. Every visual resource (stylesheets, images, fonts, small media) is stored once per snapshot and the page is relinked to that local folder, so the result browses offline and exports as a portable zip.
It is an archiving tool, not a cloning tool: downloads (archives, installers, documents), attachments and oversized files are never copied — links to them keep pointing at the live web. Nothing of a site's backend is reproduced.
Features
- Single page or crawl — depth 0–5, scope = same site / whole domain / under this path, page limit, exclude patterns.
- Crawl hygiene — a built-in dictionary skips action and non-content links (
?add-to-cart=,/cart/,/login, feeds, search results,?orderby=/filter parameters,rel="nofollow"),/aboutand/about/count as one page, and at most N query variants of the same path are queued (facet guard). Everything skipped is listed per page. - Outputs per page — relinked HTML, full-page screenshot, thumbnail, reader view (Readability), optional PDF, metadata, capture log.
- Fidelity helpers — ad/tracker blocking, cookie-banner dismissal, desktop or mobile viewport, auto-scroll for lazy content.
- Grow a snapshot later — raise the page limit or depth of a finished capture ("Expand"): Amber re-reads the links of the pages it already has and fetches only what is missing, then rewrites the links between old and new pages.
- One site, one snapshot — paste a URL of a site you already archived and the capture bar offers to add the page to that snapshot (shared files, links between its pages work) instead of creating a separate one. Pages can also be added from the snapshot's Pages tab.
- Refetch a single page — replaces one page in place with the live version, leaving the rest of the snapshot alone. Changed stylesheets are refreshed, erased elements stay erased, and if the live page now fails (or answers 4xx/5xx) the copy you have is kept.
- Links that stay inside — optional "Disable links that leave the archive": external and uncaptured links keep their look but go nowhere (reversible, the original address is kept on the element).
- Element eraser — uBlock-style picker in the preview: click a leftover cookie banner or overlay to remove it from the archived page. Edits are stored next to the pristine capture, survive any re-render, and can be restored.
- Safe replay — archived pages are served sandboxed in an opaque origin (scripts stripped by default) behind unguessable per-snapshot links.
- Library — grid/list, full-text search across page text (SQLite FTS5), tags, per-site filters, live progress over SSE.
- Export — one zip per snapshot with a site index and stubs for links that were not captured.
- Optional single password (
AMBER_PASSWORD), light/dark theme, mobile layout.
Run it
cp .env.example .env # optional; everything has defaults
npm install
npm run build
npm start # http://localhost:3000
Playwright's Chromium must be available: npx playwright install chromium on a fresh machine
(the Docker image already contains it).
Development: npm run dev (server on :3000, Vite client on :5182), npm test, npm run check.
Docker
docker compose up -d --build
Data (database + archives) lives in the /data volume. Chromium needs shm_size (set in the
compose file; on Unraid put --shm-size=1g in "Extra Parameters") and runs with --no-sandbox
inside the container.
Plain HTTP by IP on a home server works out of the box. For HTTPS, put a reverse proxy in front
and set TRUST_PROXY=true; Amber does not send HSTS or upgrade-insecure-requests itself.
Layout
server/ Express API + capture engine
lib/serializer runs inside the page: DOM -> static markup (CSSOM, shadow DOM, lazy images…)
lib/rewrite relinks HTML/CSS to the local asset store
services/capture one page: load, settle, screenshot/pdf/reader, serialize, relink, discover links
services/queue bounded-concurrency worker + snapshot finalisation + expand + SSE events
services/relink re-renders pages from raw.html with no network: final link resolution, dead links, edits
lib/picker the element eraser injected into the ?edit=1 variant of a replay page
routes/archive sandboxed replay behind per-snapshot capability tokens
client/ Svelte 5 + Vite, design tokens in src/app.css
data/
amber.db SQLite (WAL)
archives/<id>/pages/<urlHash>/{index.html,raw.html,edits.json,screenshot.png,thumb.jpg,article.html,page.pdf,meta.json}
archives/<id>/assets/<hash>.<ext>
Environment
| Variable | Default | Purpose |
|---|---|---|
PORT |
3000 | Listen port |
DATA_DIR |
./data | Database + archives |
AMBER_PASSWORD |
— | Lock the app behind a password |
AMBER_SECRET |
generated | Signs sessions and archive links (persisted in the DB when empty) |
TRUST_PROXY |
false | Behind nginx/caddy/traefik |
CONCURRENCY |
2 | Initial parallel captures (editable in Settings) |
BROWSER_ARGS |
— | Extra Chromium flags |