Catch the drift between dev and live. A zero-build, zero-dependency dev tool
that frames your local site and production side-by-side on the same route,
locked to the same scroll — then overlays them in difference mode so the only
things that light up are the pixels that actually changed.
Same route. Same scroll. Every change visible.
No install — run it with npx (needs Node ≥ 18):
npx sitedrift /pricing \
--dev http://localhost:4321 \
--live https://example.com \
--openThat boots the viewer on http://127.0.0.1:4178, opens it at /pricing, and
puts your dev build on the left and production on the right. Type any route in
the toolbar and both panes follow.
Install it globally if you reach for it often:
npm i -g sitedrift
sitedrift /pricing -d http://localhost:4321 -l https://example.com -oBind securely to loopback while using an existing local DNS name and certificate:
sitedrift / --host 127.0.0.1 --hostname compare.homelab \
--cert /path/to/fullchain.pem --key /path/to/compare.homelab.keyFor a project you use repeatedly, add sitedrift.config.json:
{
"dev": "http://localhost:4321",
"live": "https://example.com",
"author": "joe",
"open": true
}Configuration precedence is flag > environment > project file > default.
The file is discovered from the current directory upward; --config <file>
selects one explicitly.
The default (http://127.0.0.1) just works — loopback is a browser "secure
context", so you usually need nothing. When you do want HTTPS:
sitedrift --setup-https # one-time: generate + trust a local cert
sitedrift --https # serve over HTTPS from then on--setup-https uses mkcert if it's
installed — that gives a locally-trusted cert with zero browser warnings. If
mkcert isn't found it falls back to an openssl self-signed cert and prints the
one command to trust it on your OS. Already have a cert? Skip all of this and
pass --cert <file> --key <file>.
Turn every non-production Cloudflare Pages deployment into a compact sitedrift review URL. The deployment opens its own preview in DEV Solo mode and can switch to Split, Overlay, or Diff against the configured production site.
Install sitedrift, then let init scaffold the integration:
npm install --save-dev sitedrift@latest
npx sitedrift cloudflare init --live https://example.cominit writes the scoped Pages Function (functions/__sitedrift/[[path]].ts),
detects your build output directory, and prints the exact build line to paste
after your framework build:
{
"scripts": {
"build": "astro build && sitedrift cloudflare --dir dist --live https://example.com"
}
}(--dir is auto-detected at build time, so the wrapper also works as just
sitedrift cloudflare --live https://example.com.) On Cloudflare Pages, the
wrapper activates only when CF_PAGES=1 and CF_PAGES_BRANCH is not main.
Production builds are left unchanged. Use --production-branch <name> when
production is another branch.
Hosted proxies are read-only (GET/HEAD) and fixed to the configured live
origin. Frames run the compared site's scripts so interactive previews behave
like the deployment; only enable the addon for preview code you trust. Review notes stay in that
browser's localStorage; they are not sent to an API, shared with agents, or
written to disk. Existing application Functions keep their original routes.
That is two project changes and requires no Cloudflare dashboard settings or bindings. See the complete Cloudflare Pages guide for framework examples, the production guard, security details, and a CI check.
This is the same integration running on
jseverino.com. A GitHub branch
push created an ordinary Cloudflare Pages preview tied to one repository,
branch, commit, and immutable deployment URL:
The existing Cloudflare build generated 83 static pages, then the installed sitedrift dependency wrapped those pages before Cloudflare uploaded the assets and scoped Function. There was no separate deployment service or dashboard configuration:
Opening that immutable deployment produces the review UI. DEV is a temporary
red-brand branch generated with
branding-engine; LIVE is the
unchanged navy production site:
The source branch was later restored to navy, while the immutable deployment remains a reproducible artifact of the exact reviewed commit.
The included example compares a fictional Northstar release candidate against production. It uses realistic release drift: primary CTA, metric values, and a release badge change while the underlying layout stays aligned.
Pixel difference Overlay both pages and light up only what changed. |
Human + AI review channel Share route-specific findings through the viewer, CLI, or MCP. |
Focused mobile review
Narrow screens default to Solo; Swap flips between DEV and LIVE, and Overlay
surfaces a Diff control so you can isolate changes on a phone too.
Rebuild these screenshots from the deterministic local showcase:
npm run docs:screenshots- One view switch — Split (divider) · Solo (one pane, Swap flips) · Overlay
(stacked). In Overlay an opacity slider blends the panes and Diff
(
mix-blend-mode: difference) lights up only the changed pixels. Overlay force-locks scrolling so the panes can't drift. Keys:Ooverlay,Ddiff. - Locked scrolling with one controller (exact pixel or proportional) — no duplicate scrollbars, no bounce. An internal link click mirrors to both panes.
- Metadata diff + response details — title / description / canonical
compared across sides (
≠ meta). Each200/3xx/4xx/5xx/ERRbadge shows the response time on hover; click it for DEV/LIVE response, DOM-ready, load, transfer, headers, and deltas. - SEO panel — Google-style snippet preview + a ~13-point checklist per pane (title/description length, single H1, canonical, viewport, lang, Open Graph, noindex, image alt…), with a flag showing how many checks fail.
- Review notes as a shared channel — author/route/side-tagged notes in a JSON file the viewer polls every 4s, so a teammate or an AI coding session can leave notes that appear live. Click a note to jump to its route, copy a per-note deep link, dock or float the drawer, and Send to vault or export Markdown.
- No runtime dependencies. Node standard library only.
- Deploy-preview mode for Cloudflare Pages. Preview branches can carry the compact comparison toolbar without changing production output or application API routes.
| Key | Action |
|---|---|
O |
Toggle Overlay |
D |
Toggle the Overlay difference blend |
S |
Swap sides |
R |
Reload both panes |
0 |
Reset divider to 50/50 |
/ |
Focus the route field |
Space / ⇧Space |
Page down / up (when scrolling is linked) |
↑ ↓ |
Line scroll both panes |
Esc |
Close the notes drawer / open popovers |
Shortcuts work whether focus is in the viewer chrome or inside either pane.
Every option is a CLI flag, and also reads a SITEDRIFT_<NAME> env var.
| Flag | Env | Default | Purpose |
|---|---|---|---|
-d, --dev <url> |
SITEDRIFT_DEV |
http://127.0.0.1:4321 |
Left-pane (dev) origin. |
-l, --live <url> |
SITEDRIFT_LIVE |
required | Right-pane (live) origin. |
-p, --port <n> |
SITEDRIFT_PORT |
4178 |
Listen port. |
--host <addr> |
SITEDRIFT_HOST |
127.0.0.1 |
Bind address. |
--hostname <name> |
SITEDRIFT_HOSTNAME |
bind address | Browser-facing local DNS name; the socket remains bound to --host. |
-o, --open |
— | off | Open the viewer in your browser. |
--https |
— | off | Serve HTTPS with an auto cert (mkcert if present, else openssl). |
--setup-https |
— | — | One-time: generate + trust a local cert, then exit. |
--http |
— | — | Force plain HTTP (the default; overrides --https). |
--cert <file> / --key <file> |
SITEDRIFT_CERT / _KEY |
— | Bring your own cert; if both set, serve over HTTPS. |
--notes <file> |
SITEDRIFT_NOTES |
$TMPDIR/sitedrift-notes.json |
Shared review-notes file. |
--brand <text> |
SITEDRIFT_BRAND |
— | Strip | <text> from titles in pane headers. |
--author <name> |
SITEDRIFT_AUTHOR |
you |
Byline for notes added in the viewer. |
--vault <dir> |
SITEDRIFT_VAULT |
— | Enable Send to vault (writes the review markdown here). |
--config <file> |
— | discovered | Read project configuration from JSON. |
A positional [path] (e.g. sitedrift /pricing) sets the initial route.
-h, --help and -v, --version do what you'd expect.
The running process writes a private mode-0600 descriptor under
~/.sitedrift/sessions/. The npm package includes a zero-dependency stdio MCP
server and an AGENTS.md operating guide.
Configure an MCP host with:
{
"command": "sitedrift-mcp",
"args": []
}Codex and Claude Code can register it directly:
codex mcp add sitedrift -- sitedrift-mcp
claude mcp add sitedrift -- sitedrift-mcpWithout a global install:
{
"command": "npx",
"args": ["-y", "sitedrift", "mcp"]
}Agents call sitedrift_context first, then use the note tools to share findings
with the user. The server also exposes sitedrift://guide, a review_route
prompt, and sitedrift_setup for install/config/HTTPS guidance.
sitedrift_notes_list returns an opaque revision. Pass that value to
sitedrift_notes_watch to wait until notes change:
{
"port": 4178,
"revision": "6c12dbe41a39c2f7",
"timeoutMs": 25000
}The watch polls only inside the MCP tool call. A changed response contains the
same notes payload as a normal list; an unchanged timeout returns only
changed: false and the revision, avoiding repeated model-visible list output.
For hosts without MCP, use the JSON CLI instead of scraping the viewer or constructing authenticated requests:
sitedrift context
sitedrift notes list
sitedrift notes add "CTA differs" --route /pricing --side live --author codex
sitedrift notes resolve <id>
sitedrift notes reopen <id>
sitedrift notes remove <id>
sitedrift notes clearCommands print JSON and return non-zero on failure. They work over HTTP or local HTTPS without an agent-specific SDK.
| Route | Purpose |
|---|---|
GET / |
The viewer. |
GET /health |
{ dev, live, version }. |
GET /api/v1/session |
Authenticated session context and capabilities. |
GET /api/v1/notes · POST /api/v1/notes |
Authenticated note list / operations. |
GET /notes.md |
Notes as a Markdown checklist. |
POST /api/v1/notes/save |
Write notes markdown into --vault. |
GET /icon.svg |
The app mark / favicon. |
frame ports: GET /__dev/* · GET /__live/* |
Isolated proxied origins. |
The control API requires the per-session bearer token. The CLI commands above are the supported machine interface.
Most viewer state (route, layout, scroll mode, focus) is mirrored into the URL query string, so a link reproduces the exact view.
The proxy strips Content-Security-Policy, X-Frame-Options, and the
Cross-Origin-{Embedder,Opener,Resource}-Policy headers so production can be
framed next to dev.
- Only loopback hosts are accepted, and Host headers are validated.
- The viewer/control API uses
--port; DEV and LIVE frames use--port + 1and--port + 2, so neither page can inspect the other. - Framed pages communicate through a narrow
postMessagebridge and cannot access the viewer DOM, bearer token, notes API, or vault endpoint. - Treat the notes file as plaintext shared scratch space.
npm test
npm run test:e2e:visual
npm run test:e2e:visual:update # intentionally accept visual changesThe visual suite uses deterministic origins and checked-in Chromium baselines for desktop split, narrow Solo, difference overlay, and the notes drawer.
- URL rewriting is regex-based, tuned for static sites (e.g. Astro builds).
It rewrites root-relative
href/src/srcset/url(...)and Vite/_astropaths, but won't catch URLs built in JS (fetch, dynamicimport, import maps). SPAs with client-side absolute fetches may need extra rules. - Designed for two origins of the same site, not arbitrary cross-site diffing.
docs/ARCHITECTURE.md— internals, invariants, and the module map.docs/CLOUDFLARE-PAGES.md— one-command hosted preview setup, production guard, security model, and a real deployment.
Created by Joe Severino.
MIT © 2026 Joe Severino





