How it works
DemoTape runs a local proxy between your app and a public tunnel. In PR mode, it also detects your git context, looks for an existing pull request, finds or starts your app, and prepares a reviewer-ready handoff before recording the session.
PR mode preflight
When you enable PR mode, the CLI tries to make the flow feel obvious on first run:
- Git context detection — checks whether you are in a git repo and identifies the current branch and remote
- PR lookup — if GitHub CLI is available and the remote is GitHub, DemoTape looks for an existing PR for the current branch
- Startup detection — looks for common scripts like
npm run dev,pnpm dev,yarn dev, orbun run dev - Startup caching — saves the startup command per project directory so later runs skip the prompt
Port detection
When you run demotape without an explicit port, the CLI probes common dev ports. PR mode uses the same detection path.
3000, 3001, 5173, 5174, 8080, 8081, 8000, 4200, 4000, 5000, 6006 ...Each port is scored based on:
- TCP reachability — is something listening?
- HTTP response — does it return HTML?
- Framework markers — Next.js (
/_next/), Vite (/@vite/client), Nuxt, CRA, Astro, Svelte, etc. - Repo hints — config files like
next.config.js,vite.config.ts, port numbers inpackage.jsonscripts
If there's a clear winner, it's selected automatically. If multiple ports score similarly, you're prompted to choose which service reviewers should use.
Multi-port routing
If your app runs a frontend on one port and an API on another, DemoTape detects both and routes them through a single public URL. See the react-vite-node and docker-compose examples.
The main port (your frontend) is served at the root URL. Secondary ports like APIs or backend services are routed through the same public URL automatically.
All localhost URLs in your app's responses are automatically rewritten to the public URL. This includes:
- URLs in HTML, JSON, and JavaScript responses
fetch()andXMLHttpRequestcalls (intercepted at runtime)WebSocketandEventSourceconnections
What gets rewritten
Absolute URLs
Any http://localhost:PORT or http://127.0.0.1:PORT URL found in a text response body is rewritten to the public tunnel URL. WebSocket schemes (ws://) are rewritten to wss://.
Set-Cookie headers
Cookie Domain attributes pointing to localhost are stripped so the cookie works under the public domain. All other cookie attributes (Path, Secure, HttpOnly, SameSite) are preserved.
Location headers
Redirect Location headers pointing to localhost are rewritten to the public URL. Path and query strings are preserved.
All rewriting can be selectively disabled with CLI flags: --no-rewrite-cookies, --no-rewrite-absolute-urls, and --no-rewrite-location. See demotape --help for details.
Response decompression
Gzip, Deflate, and Brotli responses are decompressed before rewriting, then served uncompressed. This allows URL rewriting in compressed responses.
HTTPS
The public URL is always HTTPS. If your local dev server runs on HTTPS (e.g., with a self-signed cert), DemoTape proxies to it correctly. The tunnel handles TLS termination for the public side.
Session recording
DemoTape injects a small script before </body> in every HTML response. This script:
- Records the DOM using rrweb — captures a full visual replay of the page
- Captures console output —
log,info,warn,error,debug, plus stack traces - Captures network requests —
fetch()andXMLHttpRequestcalls with URL, status code, and timing - Tracks page navigation —
pushState,replaceState,popstate, hash changes, link clicks, form submissions, plus a 500ms polling fallback
Recording happens in the background with no visible impact on your app's performance.
Viewer overlay
The injected script also renders a small overlay UI for reviewers with:
- A live indicator showing the session is being recorded
- A countdown timer for the remaining share duration
- A comment button — viewers can leave timestamped notes with their name
- An abuse report button
Tunnel
DemoTape uses an encrypted tunnel to expose your local app to the internet. Authentication is verified on both sides — when you start sharing and when a viewer connects — before any traffic is forwarded.
Your app never needs to be deployed or publicly accessible — everything runs through the tunnel.